`timescale 1ns/1ns
`define USE_FIFO
/*
#设置命令长度,数据长度,接收命令的长度
set *(void**)0xf800000C=(48<<0)|(48<<8)|(8<<16)
#发送命令
set *(void**)0xf8000000=0x11223344
#发送数据
set *(void**)0xf8000008=0x11223344
#接收数据
print *(void**)0xf8000008
#接收回复的数据
print *(void**)0xf8000000


#sdio操作

#重启SDIO
set *(void**)0xf800000C=(48<<0)|(0<<8)|(8<<16)|(1<<31)

#重启命令
set *(void**)0xf800000C=(128<<0)|(0<<8)|(0<<16)
set *(void**)0xf8000004=0x40000000
set *(void**)0xf8000000=0x00954a00
print *(void**)0xf8000020

#发送命令CMD8(R7)
#设置命令长度
set *(void**)0xf800000C=(48<<0)|(0<<8)|(48<<16)
set *(void**)0xf8000004=0x48000001
set *(void**)0xf8000000=0xaa874300
print *(void**)0xf8000020
print *(void**)0xf8000024

#发送命令CMD55(R1),#ACMD41(R3)
set *(void**)0xf800000C=(48<<0)|(48<<8)|(48<<16)
set *(void**)0xf8000004=0x77000000
set *(void**)0xf8000000=0x00653200
set *(void**)0xf8000004=0x6940ff80
set *(void**)0xf8000000=0x00170b00
print *(void**)0xf8000024
//循环执行,直到读取的值 & 0x80不为0

#发送命令CMD2
set *(void**)0xf800000C=(48<<0)|(128<<8)|(64<<16)
set *(void**)0xf8000004=0x42000000
set *(void**)0xf8000000=0x004d2600
print *(void**)0xf8000008
print *(void**)0xf8000008
print *(void**)0xf8000008
print *(void**)0xf8000008

#CMD3(获得设备的RCA)
set *(void**)0xf800000C=(48<<0)|(48<<8)|(48<<16)
set *(void**)0xf8000004=0x43000000
set *(void**)0xf8000000=0x00211000
print *(void**)0xf8000020

#CMD7(设置RCA)
set *(void**)0xf8000004=0x47b36800
set *(void**)0xf8000000=0x00613000


#发送读取命令CMD17,设置超时命令
set *(void**)0xf8000018=0x0
set *(void**)0xf800000C=(48<<0)|(32<<8)|(48<<16)
set *(void**)0xf8000004=0x51000000
set *(void**)0xf8000000=0x00552a00
print *(void**)0xf8000020
print *(void**)0xf8000008

#切换4线模式,CMD55,ACMD6
set *(void**)0xf8000004=0x77000000
set *(void**)0xf8000000=0x00653200
set *(void**)0xf8000004=0x46000000
set *(void**)0xf8000000=0x02cb6500
print *(void**)0xf8000020
print *(void**)0xf8000024


#停止命令
-exec set *(void**)0xf8000018=0x100
-exec set *(void**)0xf8000004=0x4c000000
-exec set *(void**)0xf8000000=0x00610000


*/

module sdio (
`ifdef SIM
    sdio_cmd_test,
`endif


`ifdef  USE_FIFO
    fifo_write_data     ,
    fifo_write_en       ,
    fifo_write_rst      ,

    fifo_read_data      ,
    fifo_read_en        ,
    fifo_read_rst       ,
    fifo_empty          ,
`endif

    clk,
    rstn,
    sdio_dat,
    sdio_cmd,
    sdio_clk,
    apb_addr,
    apb_hwdata,
    apb_hrdata,
    apb_hwrite,
    apb_htran,
    apb_hready_o,
    apb_hsel
);

    input wire clk;
    input wire rstn;
    inout[3:0] sdio_dat;
    inout sdio_cmd;
    input apb_hsel;
    output reg sdio_clk;

    input[31:0] apb_addr;
    input[31:0] apb_hwdata;
    output reg [31:0] apb_hrdata;
    input apb_hwrite;
    input apb_htran;
    output reg apb_hready_o;

    reg sdio_cmd_oe = 1'b0;
    reg sdio_cmd_o = 0;
    reg sdio_dat_oe = 0;
    reg [3:0] sdio_dat_o = 0;
    assign sdio_dat = (use_gpio?gpio_oe[4]:sdio_dat_oe)?sdio_dat_o:4'bz;
`ifdef SIM
    input sdio_cmd_test;
assign sdio_cmd = (use_gpio?gpio_oe[0]:sdio_cmd_oe)?sdio_cmd_o:sdio_cmd_test;
`else
assign sdio_cmd = (use_gpio?gpio_oe[0]:sdio_cmd_oe)?sdio_cmd_o:1'bz;
`endif

`ifdef  USE_FIFO
    output wire[31:0]    fifo_write_data;
    output wire         fifo_write_en;
    output reg          fifo_write_rst;
    assign fifo_write_data = recv_fifo_data;

    input  wire[31:0]   fifo_read_data;
    output reg          fifo_read_en;
    output reg          fifo_read_rst;
    input wire          fifo_empty;
`endif


    /*
    struct {
        u64 cmd;                    //发送命令
        u32 data;                   //直接数据操作
        {
            cmd_len:8;              //命令长度
            data_len:8;             //数据长度
            cmd_recv_len:8;         //接收应答长度
            response:1;             //sdio设备接收
            cmd_send_done:1;        //
            dat_send_done:1;        //
            use_gpio:1;             //
            timeout:1;              //超时
            data_swap:1;            //数据交互
            rev2:1;                 //
            reset:1;                //31,重启
        }u32 config;
        u32  gpio_oe;               //dat0~dat3,cmd,clk
        u32  gpio;                  //dat0~dat3,cmd,clk
        u32  timeout;               //超时
        u32  rev2;
        u32  cmd_response[4];       //0x20,128位应答
        u32  recv_fifo_trigger;     //0x20,设置sdio_clk上升沿次数读取数据,读取该寄存器时,剩余次数
        u32  recv_fifo_data;        //0x24
        {
            fifo_read_rst:1;
            fifo_writ_rst:1;
            fifo_empty:1;           //
        }u32  recv_fifo_config;      //0x28 重置fifo的操作
    }
*/

    reg cmd_start;

    reg [7:0]  data_pos,cmd_send_pos;
    reg [63:0] cmd_data;
    reg [63:0] cmd_data_pr;
    reg [127:0] response_data;
    reg [31:0] send_data,send_data_pr;
    reg [31:0] recv_data;

    reg[7:0]   data_len,cmd_len;
    reg[7:0]   cmd_recv_len;

    wire[7:0]   cfg_data_len,cfg_cmd_len;
    wire[7:0]   cfg_cmd_recv_len;

    reg     [31:0] config_reg;
    wire    [31:0] config_reg_r;
    wire    cmd_send_done = $unsigned(cmd_send_pos    )>=  $unsigned(cmd_len );
    wire    dat_send_done = $unsigned(data_pos        )>=  $unsigned(data_len);
    wire    dat_swap = config_reg[29];

    assign {cfg_cmd_recv_len,cfg_data_len,cfg_cmd_len} = config_reg;
    wire    use_gpio = config_reg[27];
    reg[31:0]  gpio_oe;
    assign config_reg_r = {dat_swap,$unsigned(tick)>$unsigned(timeout),use_gpio,dat_send_done,cmd_send_done,response_flg,cfg_cmd_recv_len,cfg_data_len,cfg_cmd_len};
    reg [7:0] cmd_recv_pos;

    reg         data_send_flg = 0;
    reg         data_send_flg_cur = 0;
    reg[31:0]   data_send;

    wire    response_flg = $unsigned(cmd_recv_pos)>=$unsigned(cmd_recv_len);       //一般初始化时cmd_recv_pos会大于cmd_recv_len
    reg     transmit_data,transmit_cmd;
    reg     [31:0]  apb_hwdata_nt;
    reg     [15:0]  timeout = 'h200;
    reg     [15:0]  tick = 0;

`ifdef USE_FIFO
    //reg        recv_fifo;
    reg         recv_start = 0;          //判断接收标志,无作用.应该有软件来判断是否开始fifo传输
    reg [15:0]  fifo_write_shift;        //位移处理
    assign      fifo_write_en = fifo_write_shift[0] && recv_fifo_remaining>0;
    reg [31:0]  recv_fifo_data;
    reg [15:0]  recv_fifo_len;
    reg [15:0]  recv_fifo_remaining;
`endif

    always @(posedge clk or negedge rstn) begin
        if(!rstn||config_reg[31])begin
            `ifdef  USE_FIFO
            recv_start              <= 0;
            recv_fifo_data          <= 0;
            recv_fifo_len           <= 0;
            recv_fifo_remaining     <= 0;
            `endif

            apb_hready_o    <= 1'b1;
            sdio_cmd_o      <= 1;
            sdio_clk        <= 1;
            cmd_start       <= 0;
            data_pos        <= 'h7f;
            cmd_send_pos    <= 'h7f;
            sdio_dat_o      <= 'hf;
            sdio_dat_oe     <= 0;
            config_reg      <= 0;
            cmd_recv_pos    <= 'h7f;
            data_send_flg   <= 0;
            transmit_cmd    <= 0;
            transmit_data   <= 0;
            send_data_pr    <= 0;
            sdio_cmd_o      <= 0;
            sdio_cmd_oe     <= 0;
            timeout         <= 'h200;
            tick            <= 0;
            data_len        <= 0;
            cmd_recv_len    <= 0;
            cmd_len         <= 0;
        end
        else begin
            `ifdef USE_FIFO
            if(recv_fifo_len!=0)begin
                recv_fifo_remaining <= recv_fifo_len;
                recv_start          <= 0;
                recv_fifo_data      <= recv_data;
                fifo_write_shift    <= 16'b0001;         //sdio_clk为apb clk的2分频,而fifo为apb clk.则每16个周期写入32位数据
            end
            else if(recv_fifo_remaining>0)begin
                //读取数据
                sdio_clk <= ~sdio_clk;
                if(~sdio_clk)begin
                    recv_fifo_data          <= {recv_fifo_data[31-4:0],sdio_dat};
                    recv_fifo_remaining     <= recv_fifo_remaining - 1;
                end
                fifo_write_shift        <= {fifo_write_shift[14:0],fifo_write_shift[15]};
            end
            else
            `endif

            if(use_gpio)begin
                sdio_dat_o  <= cmd_data_pr[3:0];
                sdio_cmd_o  <= cmd_data_pr[4];
                sdio_clk    <= cmd_data_pr[5];
            end
            else if(transmit_cmd)begin
                //发送命令
                cmd_data        <=  cmd_data_pr;
                cmd_send_pos    <=  0;
                cmd_recv_pos    <=  0;
                tick            <=  0;
                cmd_recv_len    <=  cfg_cmd_recv_len;
                cmd_len         <=  cfg_cmd_len;
            end
            else if(transmit_data && dat_send_done)begin
                //发送数据
                send_data           <= send_data_pr;
                data_pos            <= 0;
                data_len            <= cfg_data_len;
                data_send_flg_cur   <= data_send_flg;
            end
            if($unsigned(cmd_send_pos)<$unsigned(cmd_len))begin
                //命令发送长度
                sdio_clk        <= ~sdio_clk;
                sdio_cmd_oe     <= 1'b1;
                if(sdio_clk)begin
                    sdio_cmd_o      <= cmd_data[63];
                    cmd_data        <= {cmd_data[62:0],1'b1};
                end
                else
                    cmd_send_pos    <= cmd_send_pos + 1;
            end
            else if(!response_flg)begin
                //device response
                sdio_clk        <= ~sdio_clk;
                sdio_cmd_oe     <= 1'b0;
                if(~sdio_clk)begin
                    //下降沿读取数据
                    if(cmd_recv_pos>0)begin
                        //开始接收,response数据
                        cmd_recv_pos    <= cmd_recv_pos + 1;
                        response_data   <= {response_data[126:0],sdio_cmd};
                    end
                    else if((timeout!=0)&& (tick > timeout))begin
                        //超时操作
                        cmd_recv_pos    <= cmd_recv_len;
                    end
                    else begin
                        //等待接收
                        if(~sdio_cmd)begin
                            cmd_recv_pos <= cmd_recv_pos + 1;
                        end
                        else
                            tick <= tick + 1;
                    end
                end
            end
            else if($unsigned(data_pos)<$unsigned(data_len))begin
                //数据开始发送或者接收
                sdio_clk            <= ~sdio_clk;
                sdio_dat_oe         <= data_send_flg_cur;
                //接收数据的时候,顺便接收sdio_cmd
                response_data       <= {response_data[126:0],sdio_cmd};
                if(~sdio_clk^data_send_flg_cur)begin
                    //发送数据的时候,上升沿更新数据.默认是高电平,也就是说下降沿时,更改数据

                    //读取数据的时候,设备在上升沿读取数据.所以需要在clk为高时获得数据
                    if(data_send_flg_cur)begin
                        //上升沿处理
                        //发送数据
                        sdio_dat_o    <=  send_data[31:32-4];
                        send_data     <= {send_data[31-4:0],4'd0};
                    end
                    else begin
                        //下降沿处理
                        //接收数据
                        recv_data    <= {recv_data[31-4:0],sdio_dat};
                    end
                end
                if(~sdio_clk)
                    data_pos      <= data_pos + 4;
            end
            else begin
                sdio_cmd_oe     <= 0;
                sdio_dat_oe     <= 0;
            end

            if(!transmit_data && dat_send_done && !apb_hready_o)begin
                //接收或者发送数据完成
                if(!dat_swap)
                    apb_hrdata      <=      recv_data;
                else
                    apb_hrdata      <=      {recv_data[7:0],recv_data[15:8],recv_data[23:16],recv_data[31:24]};

                if(data_send_flg)begin
                    apb_hready_o    <= 1'b1;
                    //这里是连续发送,继续上一个数据的发送
                    transmit_data   <= 1'b1;
                    send_data_pr    <= apb_hwdata_nt;
                end
                else if(data_send_flg^data_send_flg_cur)begin
                    //先写再读的时序,等待读的时序
                    transmit_data   <=  1'b1;
                    apb_hready_o    <=  1'b0;
                end
                else begin
                    apb_hready_o <=1'b1;
                end
                `ifdef USE_FIFO
                recv_fifo_len   <= 'd0;
                fifo_read_en    <=  0;
                `endif
            end
            else if(
                apb_htran&          //ahb总线
                apb_hsel
            )begin
                //命令
                //apb_hready_o <= 1;  //写可以是异步
                apb_hready_o <=
                    ~(!apb_hwrite&&(apb_addr[5:2]=='h2))
                    &&dat_send_done
                ;          //读取数据
                apb_hwdata_nt <= apb_hwdata;
                if(apb_hwrite)begin
                    //写入操作
                    case (apb_addr[5:2])
                        'h0:begin transmit_cmd  <= 1'b1;  cmd_data_pr[31:0]  <= apb_hwdata;             end
                        'h1:begin cmd_data_pr[63:32]    <= apb_hwdata;                                  end
                /*data*/'h2:begin transmit_data <= 1'b1;  send_data_pr<=apb_hwdata; data_send_flg  <= 1;end
                        'h3:begin config_reg        <= apb_hwdata;                                      end
                        'h4:begin gpio_oe           <= apb_hwdata;                                      end
                        'h5:begin cmd_data_pr       <= apb_hwdata;                                      end
                        'h6:begin timeout           <= apb_hwdata[15:0];  end
                        `ifdef USE_FIFO
                        //触发fifo
                        'hc:begin recv_fifo_len <= apb_hwdata[15:0]; end
                        'he:{fifo_write_rst,fifo_read_rst} <= apb_hwdata[1:0];
                        `endif
                        default:;
                    endcase
                end
                else begin
                    //读取操作
                    case (apb_addr[5:2])
                        'h0:begin apb_hrdata <= response_data[31:0] ;               end     //cmd0
                        'h1:begin apb_hrdata <= response_data[63:32];               end     //cmd1
                        'h2:begin transmit_data <= 1'b1;    data_send_flg   <= 0;   end
                        'h3:begin apb_hrdata <= config_reg_r;                       end     //读取配置寄存器
                        'h4:begin apb_hrdata <= gpio_oe;                            end
                        'h5:begin apb_hrdata <= {sdio_clk,sdio_cmd,sdio_dat};       end
                        'h6:apb_hrdata <= {16'd0,timeout};
                        'h8:apb_hrdata <= response_data[32*1-1:32*0];
                        'h9:apb_hrdata <= response_data[32*2-1:32*1];
                        'ha:apb_hrdata <= response_data[32*3-1:32*2];
                        'hb:apb_hrdata <= response_data[32*4-1:32*3];
                        `ifdef USE_FIFO
                        'hc:apb_hrdata <= recv_fifo_remaining;      //剩余接收数据
                        'hd:begin
                            if(!dat_swap)
                                apb_hrdata <= fifo_read_data;
                            else
                                apb_hrdata <= {fifo_read_data[7:0],fifo_read_data[15:8],fifo_read_data[23:16],fifo_read_data[31:24]};
                        fifo_read_en <= 1'b1; end       //读取一个fifo数据，并且触发一次fifo读取操作
                        'he:apb_hrdata <= {fifo_empty,fifo_write_rst,fifo_read_rst};
                        `endif
                        default:;
                    endcase
                end
            end
            else begin
                transmit_data  <= 0;
                transmit_cmd   <= 0;
                `ifdef USE_FIFO
                recv_fifo_len <= 'd0;
                fifo_read_en  <= 0;
                `endif
            end
        end
    end

endmodule


`ifdef SIM
module sim_tb ();
    //iverilog -DSIM -f sim.txt && vvp a.out
    reg clk = 0;
    reg rst_n = 0;
    reg memory_clk = 0;
    wire clk50m = clk;

    always clk = #10 ~clk;
    always memory_clk = #1.25 ~memory_clk;
    always rst_n = #1000 1;
    always #50000 $finish;
    reg[31:0] counter = 0;

    reg[31:0] apb_addr = 0;
    reg[31:0] apb_hwdata = 0;

    reg apb_hwrite = 0;
    reg apb_htran = 0;
    wire apb_hsel = apb_htran;
    wire apb_hready;
    reg sdio_cmd_test = 0;

    localparam test_counter = 100;

    always @(posedge clk) begin

        if(apb_hready)
            counter <= counter + 1;

        apb_htran <=
            (counter==test_counter)||
            (counter==test_counter+5)||
            (counter==test_counter+6)||
            (counter==test_counter+123) ||
            // (counter==test_counter+126) ||
            // (counter==test_counter+129) ||
            0;

        sdio_cmd_test <= counter<test_counter+125;

        if(counter==test_counter+0)begin
            apb_hwrite          <= 1;
            apb_addr[30:2]      <= 3;       //config
            apb_hwdata          <= {8'd0,8'd8,8'd32,8'd48};
        end
        else if(counter==test_counter+5)begin
            apb_hwrite          <= 1;
            apb_addr[30:2]      <= 1;       //cmd1
            apb_hwdata          <= 32'h11223344;
        end
        else if(counter==test_counter+6)begin
            apb_hwrite          <= 1;
            apb_addr[30:2]      <= 0;       //cmd0
            apb_hwdata          <= 32'h55667788;
        end
        else if(counter==test_counter+123)begin
            apb_hwrite          <= 0;
            apb_addr[30:2]      <= 2;       //data
            apb_hwdata          <= 32'haabbccdd;
        end
        // else if((counter==test_counter+126))begin
        //     apb_hwrite          <= 1;
        //     apb_addr[30:2]      <= 2;       //data
        //     apb_hwdata          <= 32'h12345678;
        // end
        // else if(counter==test_counter+129)begin
        //     apb_hwrite          <= 1;
        //     apb_addr[30:2]      <= 2;       //data
        //     apb_hwdata          <= 32'h9abcdef0;
        // end

    end


    sdio u_sdio(
        .clk(clk),
        .rstn(rst_n),

        .sdio_dat(),
        .sdio_cmd(),
        .sdio_clk(),
        .sdio_cmd_test(sdio_cmd_test),

        .apb_addr       (apb_addr       ),
        .apb_hwdata     (apb_hwdata     ),
        .apb_hrdata     (               ),
        .apb_hwrite     (apb_hwrite     ),
        .apb_htran      (apb_htran      ),
        .apb_hready_o   (apb_hready     ),
        .apb_hsel       (apb_hsel       )
    );

    initial begin
        $dumpfile("test.vcd");
        $dumpvars(0, sim_tb);
    end
endmodule
`endif
